package com.lottery.material.util;

import net.sf.json.JSONObject;
import org.apache.commons.lang3.StringUtils;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.net.URLEncoder;
import java.util.Iterator;
import java.util.Map;

/**
 * Description：文件相关操作辅助类（包括文件的拷贝、删除，获取文件的扩展名、文件名称，保存文件内容打印异常信息）。
 * Created by 朱正磊 on 2019-04-16 19:18
 */
@SuppressWarnings("ALL")
public class FileTool {

    //文件夹分隔符（使用"/"作为文件夹分隔符也可以，因为系统会自动进行转换）
    private final static String FOLDER_SEPARATOR = "\\";

    //文件后缀名分隔符
    private final static char EXTENSION_SEPARATOR = '.';

    //换行
    private final static String NEW_LINE = "\r\n";

    //文件路径（其实文件路径可以没有，直接写到文件里面即可）
    private final static String FILE_PATH = "E:\\my_log\\my_log.txt";

    //文件（参数可以直接写具体的文件路径）
    private final static File FILE = new File(FILE_PATH);

    //IE浏览器的关键字
    private final static String[] IEBrowserSignals = {"MSIE", "Trident", "Edge"};

    //当前日期（变化的，final修饰并不影响）
    //private final static String NOW_DATE = CalendarUtil.getNowDate("yyyy-MM-dd HH:mm:ss");

    /**
     * @description 获取当前路径，比如打印输出：D:\workspace\project\ideaProject\demo\. 当前目录就是demo目录。
     * @return 返回当前路径
     */
    public static String getPresentPath(){
        //D:\workspace\project\ideaProject\demo
        //return System.getProperty("user.dir");//user.dir指定了当前的路径
        return new File(".").getAbsolutePath();
    }

    /**
     * @description 根据文件全路径生成文件或者文件夹（如果是文件，不生成文件内容）
     * @param fileName 文件全路径（文件夹+文件）
     * @return 返回生成的文件或者文件夹
     */
    public static File createFile(String fileName) throws IOException {
		/*File file = new File(fileName);
		//if(file.isDirectory()){//如果文件是文件夹（必须存在才能判断）
			//file.mkdirs();//创建文件夹
		//}else if(file.isFile()){//如果文件是文件（必须存在才能判断）
			if(!file.getParentFile().exists()){//如果父文件（文件夹）不存在
				file.getParentFile().mkdirs();//只会创建文件夹，不会创建文件
			}
			if(!file.exists()){//如果文件不存在
				file.createNewFile();//创建文件
			}
		//}*/
        File file = new File(fileName);
        //if(fileName.indexOf(".") == -1){//文件夹
        if(!fileName.contains(".")){//文件夹
            if(!file.exists()){//如果文件夹不存在
                file.mkdirs();//创建文件夹
            }
        }else{//文件
            if(!file.getParentFile().exists()){//如果父文件（文件夹）不存在
                file.getParentFile().mkdirs();//只会创建文件夹，不会创建文件
            }
            if(!file.exists()){//如果文件不存在
                file.createNewFile();//创建文件
            }
        }
        return file;
    }

    /**
     * @description copy文件或者文件夹
     * @param inputFile 源文件
     * @param outputFile 目的文件
     * @param isOverWrite 是否允许覆盖（只针对文件），true表示允许，false表示不允许
     * @throws IOException IO流异常
     */
    public static void copy(File inputFile, File outputFile,
                            boolean isOverWrite) throws IOException {
        if(!inputFile.exists()){//如果源文件不存在
            throw new RuntimeException(inputFile.getPath()+"源文件不存在！");//抛出运行时异常不再向下执行
        }
        copyByRecursion(inputFile,outputFile,isOverWrite);//递归copy文件或者文件夹
    }

    /**
     * @description 为copy做递归使用
     * @param inputFile 源文件
     * @param outputFile 目的文件
     * @param isOverWrite 是否允许覆盖（只针对文件），true表示允许，false表示不允许
     * @throws IOException IO流异常
     */
    public static void copyByRecursion(File inputFile, File outputFile,
                                       boolean isOverWrite) throws IOException {
        if(inputFile.isFile()){//如果源文件是个文件
            copySingleFile(inputFile,outputFile,isOverWrite);//copy单个文件
        }else{//如果源文件是个文件夹
            if(isFileNotDir(outputFile)){//单个文件而非文件夹
                throw new RuntimeException("确保是文件夹而不是单个文件！");//如果是单个文件而不是文件夹，就直接抛出运行时异常，停止程序继续向下执行
            }
            if(!outputFile.exists()){//如果目的文件夹不存在
                outputFile.mkdirs();//创建目的文件夹，最好使用mkdirs，少用mkdir
            }
            for(File child : inputFile.listFiles()){//循环子文件夹
                copy(child,new File(outputFile.getPath()+FOLDER_SEPARATOR+child.getName()),isOverWrite);
            }
        }
    }

    /**
     * @description copy单个文件
     * @param inputFile 源文件
     * @param outputFile 目的文件
     * @param isOverWrite 是否允许覆盖（只针对文件），true表示允许，false表示不允许
     * @throws IOException IO流异常
     */
    public static void copySingleFile(File inputFile, File outputFile,
                                      boolean isOverWrite) throws IOException {
        if(outputFile.exists()){//如果目的文件已经存在
            if(isOverWrite){//允许覆盖
                if(!outputFile.delete()){//如果已存在的目的文件删除不成功
                    throw new RuntimeException(outputFile.getPath()+"无法覆盖！");//抛出运行时异常不再向下执行
                }
            }else{//不允许覆盖
                return;
            }
        }

        //判断是不是单个文件
        //方式一
		/*String fileName = outputFile.getName();//getName()获取文件名（含有后缀），不含路径
		if(!isFileNotDir(fileName)){//文件夹*/
        //方式二
		/*String filePath = outputFile.getPath();//getPath()获取文件全路径
		if(!isFileNotDir(filePath)){//文件夹*/
        //方式三
        if(!isFileNotDir(outputFile)){//文件夹
            throw new RuntimeException("确保是文件而不是文件夹！");//如果是文件夹而不是单个文件，就直接抛出运行时异常，停止程序继续向下执行
        }

        //验证文件后缀名称是否一致
        if(!getFileNameExtension(inputFile.getPath())
                .equals(getFileNameExtension(outputFile.getPath()))){
            throw new RuntimeException("后缀名不一致！");//如果文件后缀名称不一致，就直接抛出运行时异常，停止程序继续向下执行
        }

        if(!outputFile.getParentFile().exists()){//如果父文件（文件夹）不存在
            outputFile.getParentFile().mkdirs();//只会创建文件夹，不会创建文件
        }

        //new FileInputStream(File file)的好处是如果文件不存在就创建，当然存在的话就使用已存在的！
        InputStream in = new FileInputStream(inputFile);
        OutputStream out = new FileOutputStream(outputFile);
        byte[] buffer = new byte[1024];//缓冲器
        int len = 0;
        while((len = in.read(buffer)) != -1){//缓冲器一次性读取输入流里面的1024个字节
            out.write(buffer,0,len);//将buffer里面1024个字节的数据写入到输出流里面，写完之后缓冲器存储清空，依次循环
        }
        out.close();//就近原则先关闭输出流
        in.close();//就近原则后关闭输入流
    }

    /**
     * @description 删除文件或者文件夹
     * @param file 待删除的文件或者文件夹
     * @return boolean true：删除成功，false：删除失败
     */
    public static boolean delete(File file){
        return deleteFile(file);
    }

    /**
     * @description 删除文件或者文件夹，内部递归使用
     * @param file 待删除的文件
     * @return boolean true：删除成功，false：删除失败
     */
    public static boolean deleteFile(File file){
        if(null == file || !file.exists()){//如果文件为空或者文件根本不存在
            return false;
        }
        if(!file.isDirectory()){//如果文件不是文件夹（目录），即单个文件
            boolean delFlag = file.delete();
            if(!delFlag){//如果删除失败
                return false;
            }else{//如果删除成功
                return true;
            }
        }
        return deleteDir(file);
    }

    /**
     * @description 删除文件夹（目录）
     * @param dir 文件夹（目录）
     * @return boolean true：删除成功，false：删除失败
     */
    private static boolean deleteDir(File dir) {
        if(dir.isDirectory()){//如果文件是文件夹（目录）
            String[] children = dir.list();
            for (int i=0; i<children.length; i++) {//递归删除目录中的子目录
                boolean flag = deleteDir(new File(dir, children[i]));
                if (!flag) {//如果删除不成功，返回false
                    return false;
                }
            }
        }
        //删除单个文件或者空目录
        return dir.delete();
    }

    /**
     * @description 从文件路径中抽取文件的扩展名，比如aaa.txt的扩展名就是txt
     * @param path 文件全路径
     * @return 如果path为null，直接返回null
     */
    public static String getFileNameExtension(String path){
        if(path == null){//如果文件路径为null
            return null;
        }

        int extensionIndex = path.lastIndexOf(EXTENSION_SEPARATOR);// "."最后一次出现的位置，其实只有一个
        if(extensionIndex == -1){//如果文件路径不含有"."
            return null;
        }

        int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR);// "\"最后一次出现的位置
        if(folderIndex > extensionIndex){
            return null;
        }

        return path.substring(extensionIndex + 1);//从索引extensionIndex + 1开始截取直到末尾，即不包括"."
    }

    /**
     * @description 从文件路径中抽取文件名，比如从"E:\aaa\bbb.txt"路径中抽取的文件名就是bbb.txt
     * @param path 文件全路径
     * @return 抽取出来的文件名, 如果path为null，直接返回null
     */
    public static String getFileName(String path){
        if(path == null){//如果文件路径为null
            return null;
        }
        int extensionIndex = path.lastIndexOf(EXTENSION_SEPARATOR);
        int folderIndex = path.lastIndexOf(FOLDER_SEPARATOR);
        return extensionIndex == -1 ? "" : path.substring(folderIndex + 1);
    }

    /**
     * 自我感觉这个方法不是很好。。。
     * @description 根据文件名称判断文件是文件还是文件夹
     * @param fileName 文件名称，可以是文件全路径，也可以仅仅只是文件名称而已（包括后缀）
     * @return true文件，false文件夹
     */
    public static boolean isFileNotDir(String fileName){
        boolean isPath = false;//true代表是文件全路径；false代表只是文件名而已（不包含目录）（包括后缀）
        if(fileName.contains(FOLDER_SEPARATOR)){//包含"\"
            isPath = true;
        }
        String suf ;
        if(isPath){//文件全路径
            int folderIndex = fileName.lastIndexOf(FOLDER_SEPARATOR);//"\"最后出现的索引位置
            suf = fileName.substring(folderIndex + 1);//文件全路径最后一层
        }else{//仅仅只是文件名称，不包含路径
            suf = fileName;
        }
        if(suf.contains(".")){//含有"."则是文件
            return true;
        }
        return false;
    }

    /**
     * @description 根据文件判断文件是文件还是文件夹
     * @param file 文件对象
     * @return true文件，false文件夹
     */
    public static boolean isFileNotDir(File file){
		/*String filePath = file.getPath();//文件全路径
		int folderIndex = filePath.lastIndexOf(FOLDER_SEPARATOR);//"\"最后出现的索引位置
		String fileName = filePath.substring(folderIndex + 1);//文件全路径最后一层*/

        String fileName = file.getName();//文件名称而已（包括后缀）
        if(fileName.contains(".")){//含有"."则是文件
            return true;
        }
        return false;
    }

    /**
     * @description 保存文件（不建议使用，如果得到的是字节数组建议使用byteArrayToString进行转换）
     * @param content 文件内容（参数类型是字节数组还是字符串根据需求来）
     * @param file 保存的文件
     * @throws IOException IO流异常
     */
    public static void save(byte[] content, File file) throws IOException {
        if(content == null){
            throw new RuntimeException("文件内容不能为空！");//抛出运行时异常，中断（终止）程序运行
        }
        if(file == null){
            throw new RuntimeException("保存文件不能为空！");//抛出运行时异常，停止程序继续向下执行
        }
        InputStream inputStream = new ByteArrayInputStream(content);
        save(inputStream, file);
    }

    /**
     * @description 字节数组转字符串
     * @param byteArray
     * @return
     */
    public static String byteArrayToString(byte[] byteArray){
        return new String(byteArray);
    }

    /**
     * @description 保存文件
     * @param content 文件内容（参数类型是字节数组还是字符串根据需求来）
     * @param file 保存的文件
     * @throws IOException IO流异常
     */
    public static void save(String content, File file) throws IOException {
        if(content == null){
            throw new RuntimeException("文件内容不能为空！");//抛出运行时异常，中断（终止）程序运行
        }
        if(file == null){
            throw new RuntimeException("保存文件不能为空！");//抛出运行时异常，停止程序继续向下执行
        }
        byte[] bytes = content.getBytes();//字符串转换为字节数组
        InputStream inputStream = new ByteArrayInputStream(bytes);
        save(inputStream, file);
    }

    /**
     * @description 保存文件（说明：文件的字符编码格式必须是utf-8才不会出现中文乱码）
     * @param inputStream 输入流
     * @param file 保存到的文件
     * @throws IOException IO流异常
     */
    public static void save(InputStream inputStream, File file) throws IOException {
        if(inputStream == null){
            throw new RuntimeException("文件内容不能为空！");//抛出运行时异常，中断（终止）程序运行
        }
        if(file == null){
            throw new RuntimeException("保存文件不能为空！");//抛出运行时异常，停止程序继续向下执行
        }
        if(!file.getParentFile().exists()){//文件夹不存在就创建
            file.getParentFile().mkdirs();//创建文件夹，最好使用mkdirs，少用mkdir
        }
        //OutputStream out = new FileOutputStream(file);//覆盖原文件内容，如果文件没有就自动创建
        OutputStream out = new FileOutputStream(file,true);//true表示追加不覆盖原有的内容
        byte[] buffer = new byte[1024];//1024byte代表1KB
        int len = 0;
        //while((len = inputStream.read(buffer)) != -1){//个人认为这两种方式相同
        while((len = inputStream.read(buffer,0,1024)) != -1){
            out.write(buffer,0,len);
        }
        out.close();
        inputStream.close();
    }

    /**
     * @description 获取标准化内容
     * @param content
     * @return
     */
    public static String getContent(String content){
        return CalendarUtil.getNowDate("yyyy-MM-dd HH:mm:ss") + "：" + content + NEW_LINE;
    }

    /**
     * @description 保存文件内容（追加不覆盖）
     * @param content 待保存的文件内容
     * @throws IOException IO流异常
     */
    public static void saveContent(String content) throws IOException {
        save(getContent(content),FILE);
    }

    /**
     * @description 在日志文件中，打印异常堆栈
     * @param e 异常对象
     * @return
     */
    public static String logExceptionStack(Throwable e) {
        StringWriter errorsWriter = new StringWriter();
        e.printStackTrace(new PrintWriter(errorsWriter));
        return errorsWriter.toString();
    }

    /**
     * @description 打印异常信息到指定文件中
     * @param e 异常对象
     * @throws IOException
     */
    public static void printExceptionInfo(Throwable e) throws IOException {
        save(getContent(logExceptionStack(e)),FILE);
    }

    /**
     * @description 判断是否是IE浏览器
     * @param request 请求对象
     * @return true：IE浏览器；false：其他浏览器
     */
    private static boolean isIEBrowser(HttpServletRequest request) {
        String userAgent = request.getHeader("User-Agent");
        for (String signal : IEBrowserSignals) {
            if (userAgent.contains(signal)){
                return true;
            }
        }
        return false;
    }

    /**
     * @description 处理IE浏览器以及其他浏览器下文件名显示乱码
     * @param request 请求实体
     * @param oldFileName 待处理文件名
     * @return 返回处理过的文件名
     */
    public static String handleFileName(HttpServletRequest request, String oldFileName) {
        String newFileName = null;
        try {
            if(isIEBrowser(request)){//IE浏览器
                newFileName = URLEncoder.encode(oldFileName, "UTF-8");
                newFileName = StringUtils.replace(newFileName, "+", " ");
            }else{//其他浏览器
                newFileName = new String(oldFileName.getBytes("UTF-8"), "ISO-8859-1");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return newFileName;
    }

    /**
     * @description 将用户提交的表单信息下载成我们指定的文件
     * @param request 请求对象
     * @param response 响应对象
     * @param fileName 指定下载生成的文件名，比如：a.ini
     */
    public static void downloadForm(HttpServletRequest request, HttpServletResponse response, String fileName) {
        //表单提交的数据
        Map<String, Object> queryParams = MapUtil.getQueryParams(request);
        //转换成JSON对象
        JSONObject json = JSONObject.fromObject(queryParams);

        /* 遍历JSON，将JSON的值放至缓冲区 */
        StringBuilder sb = new StringBuilder();
        Iterator it = json.keys();
        while (it.hasNext()) {
            String key =  (String) it.next();
            String value = (String) json.get(key);
            sb.append(key).append("=").append(value).append("\n");
        }

        /*  设置文件ContentType类型，这样设置，会自动判断下载文件类型   */
        response.setContentType("application/multipart/form-data");

        /* 设置文件头：最后一个参数是设置下载文件名(假如我们叫a.ini)   */
        response.setHeader("Content-Disposition", "attachment;filename=" + fileName);

        try {
            /* 用流将数据写给前端 */
            OutputStream os = response.getOutputStream();
            os.write(sb.toString().getBytes());
            os.flush();
            os.close();
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }

    /**
     * @description 从服务器上下载文件
     * @param response 响应对象
     * @param filePath 文件在服务器上存储的绝对路径，比如：/home/a.txt
     */
    public static void downloadFile(HttpServletResponse response, String filePath) {
        //获取服务器文件
        File file = null;
        InputStream ins = null;
        try {
            file = new File(filePath);
            ins = new FileInputStream(file);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        }

        /* 设置文件ContentType类型，这样设置，会自动判断下载文件类型 */
        response.setContentType("multipart/form-data");

        /* 设置文件头：最后一个参数是设置下载文件名 */
        response.setHeader("Content-Disposition", "attachment;filename=" + file.getName());
        try {
            OutputStream os = response.getOutputStream();
            byte[] b = new byte[1024];
            int len;
            while((len = ins.read(b)) > 0) {
                os.write(b, 0, len);
            }
            os.flush();
            os.close();
            ins.close();
        } catch (IOException ioe) {
            ioe.printStackTrace();
        }
    }

    /**
     * @description 根据url路径下载文件到本地
     * @param urlString 文件访问url地址
     * @param filename 文件名
     * @param savePath 文件存储路径
     */
    public static void downloadByUrl(String urlString, String filename, String savePath) {
        try {
            //构造URL
            URL url = new URL(urlString);
            //打开连接
            URLConnection con = url.openConnection();
            //设置请求超时为5s
            con.setConnectTimeout(5 * 1000);
            //输入流
            InputStream is = con.getInputStream();
            //1K的数据缓冲
            byte[] bs = new byte[1024];
            //读取到的数据长度
            int len;
            //输出的文件流
            File sf = new File(savePath);
            if (!sf.exists()) {
                sf.mkdirs();
            }
            OutputStream os = new FileOutputStream(sf.getPath() + "\\" + filename);
            //开始读取
            while ((len = is.read(bs)) != -1) {
                os.write(bs, 0, len);
            }
            //完毕，关闭所有连接
            os.close();
            is.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}
